package Concorrentes;

import java.util.LinkedList;
import java.util.concurrent.ConcurrentLinkedQueue;

public class Trecho implements Runnable{
	private final double tamanhoTrecho = 10.0;
	private ConcurrentLinkedQueue<Carro> carros;
	private LinkedList<Carro> carrosEsperandoSentidoPraia;
	private LinkedList<Carro> carrosEsperandoSentidoSaoPaulo;
	private int id;
	private int numeroDePedestresNoTrecho;
	
	Trecho(int id){
		carros = new ConcurrentLinkedQueue<Carro>();
		carrosEsperandoSentidoPraia = new LinkedList<Carro>();
		carrosEsperandoSentidoSaoPaulo = new LinkedList<Carro>();
		this.id = id;
		this.numeroDePedestresNoTrecho = 0;
	}
	
	public double getTamanhoTrecho(){
		return tamanhoTrecho;
	}
	
	public synchronized boolean terminouTrecho(Carro carro){
		return (carro.getPosicao() > getTamanhoTrecho() && carro.getDirecao() == 0) ||
			   (carro.getPosicao() < 0 && carro.getDirecao() == 1);
	}
	
	public synchronized void addCarro(Carro carro){
		if(carro.getDirecao() == 0){
			carrosEsperandoSentidoSaoPaulo.add(carro);
		} else {
			carrosEsperandoSentidoPraia.add(carro);
		}
	}
	
	public synchronized void removeCarro(Carro carro){
		carros.remove(carro);			
	}

	public void run() {
		
		while( !Controlador.fimDaSimulacao() || Controlador.temCarrosSubindoSerra() ){
			
			// Espera Trecho anterior me acordar
			// TODO - Spurious (?) Wake Up. Como o Gold disse que acontece MUITO raramente,
			// menos ainda no Java 6, não tomei esse cuidado.
			esperaSerAcordado();
			
			// Dorme um pouco
			dorme();
			
			// Atualiza os carros do trecho
			atualizaCarros();
			acordaTrechoSeguinte();
		}	
	}

	public synchronized boolean temCarrosNoTrecho() {
		return (carros.size() > 0 || carrosEsperandoSentidoPraia.size() > 0 || carrosEsperandoSentidoSaoPaulo.size() > 0);
	}

	private synchronized void acordaTrechoSeguinte() {
		Controlador.acordaTrecho(id + 1);
	}

	private synchronized void esperaSerAcordado() {
		try {
			wait();
		} catch (InterruptedException e) {
		}		
	}

	public synchronized void acorda() {
		notify();
	}

	private void dorme() {
		try {
			Thread.sleep(10);
		} catch (InterruptedException e) {
		}
	}

	private Carro achaCarroDaFrente(Carro carro){
		double pos = carro.getPosicao();
		double dist = 11.0;
		Carro prox = null;
		for(Carro c : carros) if(c.getDirecao() == carro.getDirecao()){
			double posDaFrente = c.getPosicao();
			if(c.isNoAcostamento() || c.equals(carro) || posDaFrente < 1e-5 || posDaFrente > 10 - 1e-5) continue;
			if(carro.getDirecao() == 0){
				if(posDaFrente >= pos){
					if(posDaFrente - pos < dist){
						dist = posDaFrente - pos;
						prox = c;
					}
				}
			} else {
				if(posDaFrente <= pos){
					if(pos - posDaFrente < dist){
						dist = pos - posDaFrente;
						prox = c;
					}
				}
			}
		}		
		return prox;
	}
	
	private synchronized void colocaCarrosSentidoSaoPaulo(){
		if(carrosEsperandoSentidoSaoPaulo.size() == 0)
			return;
		
		double menorPosicao = 10.0;
		for(Carro carro : carros) if(carro.getDirecao() == 0){
			menorPosicao = Math.min(menorPosicao, carro.getPosicao());
		}
		
		// Sentido Sao Paulo - O carro da frente já está a uma distância segura, 
		// podemos adicionar o primeiro da fila ao trecho
		if(menorPosicao > 0.2){
			carros.add(carrosEsperandoSentidoSaoPaulo.poll());
		}
	}
	
	private void colocaCarrosSentidoPraia(){
		if(carrosEsperandoSentidoPraia.size() == 0)
			return;
		
		double maiorPosicao = 0.0;
		for(Carro carro : carros) if(carro.getDirecao() == 1){
			maiorPosicao = Math.max(maiorPosicao, carro.getPosicao());
		}
		
		// Sentido Praia - O carro da frente já está a uma distância segura, 
		// podemos adicionar o primeiro da fila ao trecho
		if(maiorPosicao < 9.8){
			carros.add(carrosEsperandoSentidoPraia.poll());
		}
	}
	
	private synchronized void colocaCarrosNoTrecho(){
		
		colocaCarrosSentidoSaoPaulo();
		colocaCarrosSentidoPraia();
	}
	
	private double distancia(Carro carro1, Carro carro2) {
		return Math.abs(carro1.getPosicao() - carro2.getPosicao());
	}
		
	private boolean daFrenteMaisDevagar(Carro carro, Carro daFrente){
		return (daFrente != null && daFrente.getVelocidade() < carro.getVelocidade());
	}
	
	private boolean pistaLivre(Carro carro, Carro daFrente){
		return ((daFrente == null) || (distancia(daFrente, carro) > 0.5));
	}
	
	private void acertaVelocidadeCarro(Carro carro){
		
		// Estava esperando pedestre passar
		if(!temPedestreNoTrecho() && carro.getVelocidade() == 0) 
			carro.setVelocidade(carro.getMemoriaVelocidade());
		
		// Acha o carro diretamente na frente
		Carro daFrente = achaCarroDaFrente(carro);
		
		// Acerta velocidade, se necessario
		if(daFrenteMaisDevagar(carro, daFrente) && vaiBater(carro,daFrente)){
			carro.setVelocidade(daFrente.getVelocidade());
		}
		
		// Se o carro da frente está longe (ou não tem carro na frente), e o motorista é veloz, aceleraramos
		if( pistaLivre(carro, daFrente) && carro.isVeloz()){
			carro.setVelocidade(carro.getVelocidade() + 5);
		}

		if(temPedestreAFrente(carro))
			carro.esperaPedestrePassar();
		
		// Limita a velocidade do carro
		if(carro.getVelocidade() > Controlador.getVelocidadeMaxima()){
			carro.setVelocidade(Controlador.getVelocidadeMaxima());
		}
	}
	
	private double proximaPosicao(Carro carro){
		if(carro.getDirecao() == 0)
			return (carro.getPosicao() + ((double)carro.getVelocidade()/100.0));
		else
			return (carro.getPosicao() - ((double)carro.getVelocidade()/100.0));
	}
	
	private boolean vaiBater(Carro carro, Carro daFrente) {
		if(carro.getDirecao() == 0){
			if(proximaPosicao(carro) + 0.2 >= daFrente.getPosicao())
				return true;
		} else {
			if(proximaPosicao(carro) - 0.2 <= daFrente.getPosicao())
				return true;
		}
		return false;
	}

	private boolean temPedestreAFrente(Carro carro) {
		if(!temPedestreNoTrecho())
			return false;
		
		if(carro.getDirecao() == 0 && carro.getPosicao() < (Controlador.getTamanhoTrecho()/2 - 0.2)){
			if(carro.getPosicao() + ((double)(carro.getVelocidade()))/100.0 >= Controlador.getTamanhoTrecho()/2 - 0.2){
					return true;
			}
		} else if(carro.getDirecao() == 1 && carro.getPosicao() > Controlador.getTamanhoTrecho()/2 + 0.2){
			if(carro.getPosicao() - ((double)(carro.getVelocidade()))/100.0 <= Controlador.getTamanhoTrecho()/2 + 0.2){
				return true;
			}
		}
		return false;
	}

	public synchronized boolean temPedestreNoTrecho() {
		if(numeroDePedestresNoTrecho > 0) return true;
		return false;
	}

	private void atualizaPosicao(Carro carro) {
		if(carro.getDirecao() == 0)
			carro.setPosicao(carro.getPosicao() + ((double)(carro.getVelocidade()))/100.0);
		else 
			carro.setPosicao(carro.getPosicao() - ((double)(carro.getVelocidade()))/100.0);
	}
	
	// Consideramos uma atualizacao como um step de 36 segundos (um carro anda 1/100 da sua velocidade), e o tempo
	// está em "fast forward".
	private synchronized void atualizaCarros() {
		
		// Coloca os carros que estão esperando para entrar
		colocaCarrosNoTrecho();
		
		for(Carro carro : carros){
			if(!carro.isNoAcostamento()){
				
				acertaVelocidadeCarro(carro);
				
				atualizaPosicao(carro);
				
				// Carro terminou o trecho
				if(terminouTrecho(carro)){
					Controlador.acordaCarro(carro.getId());
					removeCarro(carro);
				}
			}
		}
	}

	public synchronized void addPedestres(int i) {
		numeroDePedestresNoTrecho += i;
	}

	public synchronized void removePedestres(int i) {
		numeroDePedestresNoTrecho -= i;
	}
	
}
